package com.keray.web.push.store.impl;

import cn.hutool.core.collection.CollUtil;
import com.keray.web.push.store.AbstractSocketDataStore;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.Session;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author by keray
 * date:2021/7/26 9:52 上午
 */
@Component("memoryStore")
@Slf4j
public class MemoryStore extends AbstractSocketDataStore {

    private final Map<String/*service*/, Data/*data*/> serviceData = new ConcurrentHashMap<>();

    @Override
    public int insertSession(Session session, String service, String uid) {
        Data data = serviceData.computeIfAbsent(service, s -> new Data(service));
        int sessionIndex = super.insertSession(session, service, uid);
        // 新增uid=》sessionIndex映射
        data.addUidIndex(uid, sessionIndex);
        return sessionIndex;
    }

    @Override
    public boolean removeSession(Session session, String service, String uid) {
        var data = serviceData.get(service);
        var removeIndex = sessionIndex(session, service, uid);
        if (!super.removeSession(session, service, uid)) return false;
        data.removeUid(uid, removeIndex);
        return true;
    }

    @Override
    public boolean removeSession(String service, String uid) {
        if (!super.removeSession(service, uid)) return false;
        var data = serviceData.get(service);
        data.removeUid(uid);
        return true;
    }

    @Override
    protected LinkedList<Integer> uidSessionIndexes(String service, String uid) {
        Data data = serviceData.get(service);
        var res = data.uidIndex(uid);
        return res == null ? new LinkedList<>() : res;
    }


    @Override
    public LinkedList<Session> serviceSession(String service) {
        Data data = serviceData.get(service);
        return indexes2Sessions(data.serviceIndex());
    }

    @Override
    public LinkedList<Session> serviceAndGroupSession(String service, String group) {
        Data data = serviceData.get(service);
        return indexes2Sessions(data.groupIndex(group));
    }

    @Override
    public void groupSessionLink(String service, String group, String uid) {
        Data data = serviceData.get(service);
        data.groupLink(group, uid);
    }

    private LinkedList<Session> indexes2Sessions(List<Integer> indexs) {
        if (CollUtil.isEmpty(indexs)) {
            return new LinkedList<>();
        }
        LinkedList<Session> result = new LinkedList<>();
        for (Integer index : indexs) {
            result.add(sessionData.get(index));
        }
        return result;
    }
}

class Data {
    private final Object clock = new Object();
    private final String service;
    private List<Integer> allIndex = new LinkedList<>();
    private Map<String/*uid*/, LinkedList<Integer>/*index*/> uidStore = new HashMap<>();
    private Map<String/*uid*/, LinkedList<String>/*group*/> uidGroup = new HashMap<>();
    private Map<String/*groupId*/, LinkedList<Integer>/*index*/> groupIndex = new HashMap<>();

    public Data(String service) {
        this.service = service;
    }

    public void addUidIndex(String uid, int index) {
        synchronized (clock) {
            var old = uidStore.computeIfAbsent(uid, s -> new LinkedList<>());
            old.add(index);
            allIndex.add(index);
        }
    }

    public void groupLink(String group, String uid) {
        var indexs = uidIndex(uid);
        synchronized (clock) {
            LinkedList<String> groups = uidGroup.computeIfAbsent(uid, s -> new LinkedList<>());
            groups.add(group);
            LinkedList<Integer> groupIndexs = groupIndex.computeIfAbsent(group, s -> new LinkedList<>());
            groupIndexs.addAll(indexs);
        }
    }

    public void removeUid(String uid) {
        removeUid(uid, null);
    }

    public void removeUid(String uid, Integer index) {
        synchronized (clock) {
            var indexs = uidStore.get(uid);
            if (CollUtil.isEmpty(indexs)) {
                return;
            }
            List<String> groups = uidGroup.get(uid);
            if (index != null) {
                allIndex.remove(index);
                indexs.remove(index);
                if (indexs.isEmpty()) uidStore.remove(uid);
            } else {
                allIndex.removeAll(indexs);
                uidStore.remove(uid);
            }
            // 移除用户所有的分组
            if (groups != null) {
                for (String group : groups) {
                    if (index != null) groupIndex.get(group).remove(index);
                    else groupIndex.get(group).removeAll(indexs);
                    if (groupIndex.get(group).isEmpty()) {
                        groupIndex.remove(group);
                    }
                }
            }
            if (index == null || indexs.isEmpty()) {
                uidGroup.remove(uid);
                uidStore.remove(uid);
            }
        }
    }

    public LinkedList<Integer> uidIndex(String uid) {
        var data = uidStore.get(uid);
        return data == null ? null : new LinkedList<>() {{
            addAll(data);
        }};
    }

    public List<Integer> groupIndex(String group) {
        var data = groupIndex.get(group);
        return data == null ? null : new LinkedList<>() {{
            addAll(data);
        }};
    }

    public List<Integer> serviceIndex() {
        return new LinkedList<>() {{
            addAll(allIndex);
        }};
    }
}
